﻿// new psspeccommandbase (oising@gmail.com)
// implements common authentication and service access

using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using System.Text;

using System.Linq;
using System.Linq.Expressions;

using Microsoft.PowerShell.CmdletManagement.PsSpecCmdletService;
using System.Threading;
using System.Web.ClientServices;
using System.Net;
using System.Reflection;

namespace Microsoft.PowerShell.CmdletManagement
{
    public abstract class PsSpecCommandBase : Cmdlet
    {
        private AuthenticationType _authType;
        private PSCredential _credential;
        private Uri _serviceUri;
        private CmdletService _service;

        protected PsSpecCommandBase()
        {
            _serviceUri = PsSpecCmdletUtils.Constants.ServiceUri;
        }

        [Parameter]
        public Uri ServiceUri
        {
            get
            {
                return _serviceUri;
            }
            set
            {
                _serviceUri = value;
            }
        }

        [Parameter]
        [Credential]
        public PSCredential Credential
        {
            get
            {
                // initialize credential
                if (_credential == null)
                {
                    PSCredential credential = null;
                    
                    var principal = Thread.CurrentPrincipal as ClientRolePrincipal;
                    var identity = Thread.CurrentPrincipal.Identity as ClientFormsIdentity;

                    // being invoked from within Cmdlet Designer?
                    if (identity != null) {

                        var member = typeof(ClientFormsIdentity).GetField("_Password",
                            BindingFlags.Instance | BindingFlags.NonPublic);

                        SecureString password = member.GetValue(identity) as SecureString;

                        credential = new PSCredential(identity.Name, password);
                        _authType = AuthenticationType.Clear;
                        
                        //credential = GetIdentityFormsTicket(credential, identity);
                    }
                    else
                    {
                        WriteVerbose("Initializing with anonymous profile.");

                        SecureString guestPassword = new SecureString();
                        guestPassword.AppendChar('*');
                        guestPassword.MakeReadOnly();

                        credential = new PSCredential("guest", guestPassword);
                        _authType = AuthenticationType.Anonymous;

                        var cmdlet = (from attrib in GetType()
                                          .GetCustomAttributes(typeof (CmdletAttribute), false)
                                          .Cast<CmdletAttribute>()
                                      select attrib)
                                        .SingleOrDefault();

                        switch (cmdlet.VerbName)
                        {
                            case "Add":
                            case "New":
                            case "Remove":
                            case "Set":
                                this.WriteWarning(
                                    String.Format(
                                        "{0}-{1} using anonymous profile: some operations may be denied. Pass -Credential to authenticate.",
                                        cmdlet.VerbName,
                                        cmdlet.NounName));
                                break;

                            default: // Compare / Get
                                break;
                        }

                    }
                    _credential = credential;
                }
                return _credential;
            }
            set
            {
                _authType = AuthenticationType.Clear;
                _credential = value;                
            }
        }

        private PSCredential GetIdentityFormsTicket(PSCredential credential, ClientFormsIdentity identity)
        {
            CookieCollection authCookies = identity.AuthenticationCookies.GetCookies(_serviceUri);

            if (authCookies.Count == 1)
            {
                var password = new SecureString();

                Cookie authCookie = authCookies[0];
                string ticket = authCookie.Value;

                // prepend magic number for form ticket
                password.AppendChar((char)0xaf); // xor a
                password.AppendChar((char)0x32); // ld (ae45),a
                password.AppendChar((char)0x45);
                password.AppendChar((char)0xAE);

                foreach (char c in ticket)
                {
                    password.AppendChar(c);
                }
                password.MakeReadOnly();

                credential = new PSCredential(identity.Name, password);

                //_authType = AuthenticationType.FormTicket;

                WriteVerbose("Initializing with authenticated client forms profile: " + identity.Name);
            }
            else
            {
                WriteWarning("Found multiple authentication cookies?");
            }
            return credential;
        }

        protected CmdletService Service
        {
            get
            {
                if (_service == null)
                {
                    WriteVerbose("Initializing CmdletService");

                    var authentication = GenerateAuthenticationSoapHeader();

                    _service = new CmdletService
                                   {
                                       Url = this.ServiceUri.ToString(),
                                       AuthenticationSoapHeaderValue = authentication,
                                       UseDefaultCredentials = false
                                   };
                }
                return _service;
            }
        }

        private AuthenticationSoapHeader GenerateAuthenticationSoapHeader()
        {
            var authentication = new AuthenticationSoapHeader
            {                
                User = (this.Credential.UserName ?? String.Empty).TrimStart('\\'),
                Type = _authType
            };
            
            string passwordText;

            if (_credential.Password != null)
            {
                passwordText = ConvertToUnsecureString(_credential.Password);
            }
            else
            {
                passwordText = String.Empty;
            }
            authentication.Password = passwordText;

            return authentication;
        }

        public static string ConvertToUnsecureString(SecureString securePassword)
        {
            if (securePassword == null)
                throw new ArgumentNullException("securePassword");

            IntPtr unmanagedString = IntPtr.Zero;
            try
            {
                unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(securePassword);
                return Marshal.PtrToStringUni(unmanagedString);
            }
            finally
            {
                Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString);
            }
        }

    }
}
